use crate::{ExportAction, Info, SqlConfig};
use docx_rs::{
    AlignmentType, Docx, DocxError, Paragraph, Run, Shading, Table, TableCell, TableLayoutType,
    TableRow, WidthType,
};
use std::{
    collections::BTreeMap,
    fs::OpenOptions,
    io::{Seek, Write},
    path::Path,
};

pub(crate) struct Word {}

impl ExportAction for Word {
    fn run(
        config: &SqlConfig,
        groups: &BTreeMap<&String, Vec<&Info>>,
    ) -> Result<String, crate::RunError> {
        let output = &get_output(config);
        let path = Path::new(output);
        let mut file = OpenOptions::new()
            .create(true)
            .truncate(true)
            .write(true)
            .open(path)
            .unwrap();
        write(groups, &mut file).unwrap();
        Ok(output.to_string())
    }
}

const HEADER_FILL: &str = "#C1D6F0";
const HEADER_COLOR: &str = "#000";

pub(crate) fn write<T>(
    groups: &BTreeMap<&String, Vec<&Info>>,
    writer: &mut T,
) -> Result<(), DocxError>
where
    T: Write + Seek,
{
    let mut docx = Docx::new();
    groups
        .iter()
        .enumerate()
        .map(|(index, (name, list))| {
            let name = format!("表{}", name);
            let name = Paragraph::new()
                .outline_lvl(0)
                .add_bookmark_start(index, &name)
                .add_run(Run::new().add_text(name).size(36).bold())
                .add_bookmark_end(index)
                .indent(Some(100), None, None, None);
            let rows = table_rows(list);
            let table = Table::new(rows)
                .width(5000, WidthType::Pct)
                .layout(TableLayoutType::Autofit);
            (name, table)
        })
        .for_each(|(name, table)| {
            docx = docx
                .clone()
                .add_paragraph(name)
                .add_paragraph(empty_line())
                .add_table(table)
                .add_paragraph(empty_line())
                .add_paragraph(empty_line());
        });
    docx.build().pack(writer)?;
    Ok(())
}

fn empty_line() -> Paragraph {
    Paragraph::new()
}

fn table_rows(list: &[&Info]) -> Vec<TableRow> {
    let header = table_headers();
    let mut contents = table_contents(list);
    contents.insert(0, header);
    contents
}

fn table_contents(list: &[&Info]) -> Vec<TableRow> {
    list.iter()
        .map(|info| {
            let list = [
                text_center_cell(info.ordinal_position.to_string()),
                text_left_cell(info.column_name.clone()),
                text_center_cell(info.data_type.clone()),
                text_center_cell(info.is_nullable.clone()),
                text_left_cell(info.column_comment.clone().unwrap_or_default()),
            ];
            let cells = list.into_iter().collect::<Vec<_>>();
            TableRow::new(cells)
        })
        .collect()
}

fn table_headers() -> TableRow {
    let list = ["序号", "列名", "数据类型", "为Null", "备注"];
    let cells = list
        .iter()
        .map(header_text_cell)
        .map(|cell| {
            cell.vertical_align(docx_rs::VAlignType::Center)
                .shading(Shading::new().color(HEADER_COLOR).fill(HEADER_FILL))
        })
        .collect();
    TableRow::new(cells)
}

fn text_left_cell<T>(text: T) -> TableCell
where
    T: AsRef<str>,
{
    TableCell::new()
        .add_paragraph(
            Paragraph::new()
                .add_run(Run::new().add_text(text.as_ref()))
                .indent(Some(20), None, None, None),
        )
        .vertical_align(docx_rs::VAlignType::Center)
}

fn text_center_cell<T>(text: T) -> TableCell
where
    T: AsRef<str>,
{
    TableCell::new()
        .add_paragraph(
            Paragraph::new()
                .add_run(Run::new().add_text(text.as_ref()))
                .align(AlignmentType::Center),
        )
        .vertical_align(docx_rs::VAlignType::Center)
}
fn header_text_cell<T>(text: T) -> TableCell
where
    T: AsRef<str>,
{
    TableCell::new().add_paragraph(
        Paragraph::new()
            .add_run(Run::new().add_text(text.as_ref()).bold())
            .align(AlignmentType::Center),
    )
}
/// 获取输出路径
pub(crate) fn get_output(config: &SqlConfig) -> String {
    let name = format!("{}.docx", config.database);
    config
        .output
        .as_ref()
        .map(|name| {
            if name.ends_with(".docx") {
                name.to_string()
            } else {
                format!("{}.docx", name)
            }
        })
        .unwrap_or(name)
}
